#include "camera.h"
//#include "extras.h"
#include "particles.h"
#include <GL/glut.h>
#include <ctime>
#include <cstdlib>

namespace
{
	ParticleSystem *parsys;
	bool isShaded = true;
	bool isKey = false;
	bool isRK = true;
	bool isWind = false;
	int wid = 10, hei = 10;
	Vec3d winddir= vl_zero;
	float stepsize = .005;
	uint texture;
    // THE FOLLOWING ITEMS ARE SPECIFIC TO THE PARTICLE SYSTEM.
	int particleNumber = 100;
    // This is a filler function, implementing a particle shower.
    void initSystem()
    {
        srand(time(NULL));
		parsys = new ParticleSystem(wid*hei);
		//parsys->initFountain(vl_zero, -1);
		//parsys->initChain(Vec3d(0,4,0));
		parsys->initCloth(wid, hei, true);
		//parsys->r = 1;
		//parsys->initFireworks(vl_zero, 2);

    }

    // Take a step forward for the particle shower
    void stepSystem(float dt)
    {
    	
    	if(isWind)
    		for(int i=0; i<parsys->pnum; i++)
    		{
    			srand(time(NULL));
    			parsys->particles[i].f += winddir+Vec3d(double(rand())/RAND_MAX-.5, 0, double(rand())/RAND_MAX-.5)*50;//*(1+double(rand())/RAND_MAX);
    		}
    	//else
    	//	for(int i=0; i<parsys->pnum; i++)
    	//		parsys->particles[i].f = vl_zero;
    	if(isRK)
			parsys->rkStep(dt);
		else
			parsys->eulerStep(dt);
		parsys->computeCollition(Vec3d(0, 0, -2), 4.3);
		parsys->computeCollition(Vec3d(0,-4,0), Vec3d(0,1,0));
		//parsys->computeCollition(Vec3d(0, 0, -2), 4.2);
		//parsys->computeCollition(Vec3d(0, -15, -5), 4.2);
    }      

    //-------------------------------------------------------------------
    
        
    // This is the camera
    Camera camera;

    // These are state variables for the UI
    bool gMousePressed = false;
    bool isMovingCamera = false;

    // Declarations of functions whose implementations occur later.
    void arcballRotation(int endX, int endY);
    void keyboardFunc( unsigned char key, int x, int y);
    void specialFunc( int key, int x, int y );
    void mouseFunc(int button, int state, int x, int y);
    void motionFunc(int x, int y);
    void reshapeFunc(int w, int h);
    void drawScene(void);
    void initRendering();

    // This function is called whenever a "Normal" key press is
    // received.
    void keyboardFunc( unsigned char key, int x, int y )
    {
        switch ( key )
        {
        case 27: // Escape key
            exit(0);
            break;
        case 'f':
        	if(isShaded)
        		isShaded = false;
        	else
        		isShaded = true;
        	break;
        case 'h':
        	if(parsys->isHanged)
        	{
        		parsys->isHanged = false;
        		for(int i=0; i<parsys->pnum ; i++)
        			parsys->particles[i].v = vl_zero;
        	}
        	else
        		parsys->isHanged = true;
        	break;
        case ' ':
        {
            Mat4d eye; eye.MakeDiag();
            camera.SetRotation(eye);
            camera.SetCenter(Vec3d(0,0,0));
            break;
        }
		case 'q':
		{
			parsys->particles[0].x[0]+=1;
			break;
		}
		case 'e':
		{
			parsys->particles[0].x[0]-=1;
			break;
		}
		case 'w':
		{
			if(isWind)
			{
				//for(int i=0; i<parsys->pnum; i++)
				//	parsys->particles[i].f = vl_zero;
				isWind = false;
			}
			else
			{
				srand(time(NULL));
				winddir= Vec3d(double(rand())/RAND_MAX-.5, 0, double(rand())/RAND_MAX-.5)*500;
				isWind = true;
				
			}
		}
        default:
            cout << "Unhandled key press " << key << "." << endl;        
        }

        glutPostRedisplay();
    }

    // This function is called whenever a "Special" key press is
    // received.  Right now, it's handling the arrow keys.
    void specialFunc( int key, int x, int y )
    {
        switch ( key )
        {
		
        }

        //glutPostRedisplay();
    }

    //  Called when mouse button is pressed.
    void mouseFunc(int button, int state, int x, int y)
    {
        if (state == GLUT_DOWN)
        {
            gMousePressed = true;
            
            switch (button)
            {
            case GLUT_LEFT_BUTTON:
            	isMovingCamera = true;
                camera.MouseClick(Camera::LEFT, x, y);
                break;
            case GLUT_MIDDLE_BUTTON:
                //camera.MouseClick(Camera::MIDDLE, x, y);
                break;
            case GLUT_RIGHT_BUTTON:
            	isMovingCamera=true;
                camera.MouseClick(Camera::RIGHT, x,y);
            case 4:
            	parsys->particles[0].x[1]-=.2;
            	parsys->particles[parsys->w-1].x[1]-=.2;
            	break;
            case 3:
            	parsys->particles[0].x[1]+=.2;
            	parsys->particles[parsys->w-1].x[1]+=.2;
            	break;
            default:
                break;
            }                       
        }
        else
        {
            camera.MouseRelease(x,y);
            gMousePressed = false;
            isMovingCamera = false;
        }
        glutPostRedisplay();
    }

    // Called when mouse is moved while button pressed.
    void motionFunc(int x, int y)
    {
    	
    	if(isMovingCamera)
        	camera.MouseDrag(x,y); 
        else
        {     
        	static int xlast = x, ylast = y;  
	    	parsys->particles[0].x[0] += (x-xlast)*.1;
	    	parsys->particles[0].x[2] += (y-ylast)*.1;
	    	if(parsys->psType == 3)
	    	{
	    		parsys->particles[parsys->w-1].x[0] += (x-xlast)*.1;
	    		parsys->particles[parsys->w-1].x[2] += (y-ylast)*.1;
	    	}
	        glutPostRedisplay();
	        xlast = x;
	        ylast = y;
        }
    }

    // Called when the window is resized
    // w, h - width and height of the window in pixels.
    void reshapeFunc(int w, int h)
    {
        camera.SetDimensions(w,h);

        camera.SetViewport(0,0,w,h);
        camera.ApplyViewport();

        // Set up a perspective view, with square aspect ratio
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();

        camera.SetPerspective(50);
        camera.ApplyPerspective();
    }

    // Initialize OpenGL's rendering modes
    void initRendering()
    {
		//parsys->loadTexture("bla.bmp");
    	glEnable(GL_TEXTURE_2D);
        glEnable(GL_DEPTH_TEST);   // Depth testing must be turned on
        glEnable(GL_LIGHTING);     // Enable lighting calculations
        glEnable(GL_LIGHT0);       // Turn on light #0.
        glEnable(GL_LIGHT1);

        glEnable(GL_NORMALIZE);

        // Setup polygon drawing
        glShadeModel(GL_SMOOTH);
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        
        // Clear to black
        glClearColor(0,0,0,1);

    }

    // This function is responsible for displaying the object.
    void drawScene(void)
    {
        // Clear the rendering window
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glMatrixMode( GL_MODELVIEW );  
        glLoadIdentity();              

        // Light color (RGBA)
        GLfloat Lt0diff[] = {1.0,1.0,1.0,1.0};
        GLfloat Lt0pos[] = {3.0,3.0,5.0,1.0};
        glLightfv(GL_LIGHT0, GL_DIFFUSE, Lt0diff);
        glLightfv(GL_LIGHT0, GL_POSITION, Lt0pos);
        glLightfv(GL_LIGHT1, GL_SPECULAR, Lt0diff);
        glLightfv(GL_LIGHT1, GL_POSITION, Lt0pos);

        camera.ApplyModelview();

        // THIS IS WHERE THE DRAW CODE GOES.
        
        glPushMatrix();
        	glTranslatef(0, 0, -2);
        	glutSolidSphere(4, 20, 20);
        	//glTranslatef(0,-5, -2);
        	//glutSolidSphere(3, 20, 20);
        glPopMatrix();
        glPushMatrix();
        	glTranslatef(0,-4.01,0);
        	glScalef(50, .01, 50);
        	
        	glutSolidCube(1);
        	//glTranslatef(0, -15, -5);
        	//glutSolidSphere(3, 20, 20);
        glPopMatrix();

		glBindTexture(GL_TEXTURE_2D, texture);
		parsys->draw(isShaded);

        // This draws the coordinate axes when you're rotating, to
        // keep yourself oriented.
        if (gMousePressed)
        {
            glPushMatrix();
            glTranslated(camera.GetCenter()[0], camera.GetCenter()[1], camera.GetCenter()[2]);

            // Save current state of OpenGL
            glPushAttrib(GL_ALL_ATTRIB_BITS);

            // This is to draw the axes when the mouse button is down
            glDisable(GL_LIGHTING);
            glLineWidth(3);
            glPushMatrix();
            glScaled(5.0,5.0,5.0);
            glBegin(GL_LINES);
            glColor4f(1,0.5,0.5,1); glVertex3d(0,0,0); glVertex3d(1,0,0);
            glColor4f(0.5,1,0.5,1); glVertex3d(0,0,0); glVertex3d(0,1,0);
            glColor4f(0.5,0.5,1,1); glVertex3d(0,0,0); glVertex3d(0,0,1);

            glColor4f(0.5,0.5,0.5,1);
            glVertex3d(0,0,0); glVertex3d(-1,0,0);
            glVertex3d(0,0,0); glVertex3d(0,-1,0);
            glVertex3d(0,0,0); glVertex3d(0,0,-1);

            glEnd();
            glPopMatrix();

            glPopAttrib();
            glPopMatrix();
        }
                 
        // Dump the image to the screen.
        glutSwapBuffers();
    }

    void timerFunc(int t)
    {
        stepSystem(stepsize);

        glutPostRedisplay();

        glutTimerFunc(t, &timerFunc, t);
    }

    

    
    
}

// Main routine.
// Set up OpenGL, define the callbacks and start the main loop
int main( int argc, char* argv[] )
{
	// Setup particle system
    
	if(argc > 2)
	{
		if(argv[1][0] == 'e')
			isRK = false;
		stepsize = atof(argv[2]);
		if(argc == 6)
		{
			wid = atoi(argv[4]);
			hei = atoi(argv[5]);
		}
	}
	initSystem();
	if(argc == 4)
			if(atoi(argv[3])==1)
				parsys->isHanged = true;
			else
				parsys->isHanged = false;
    glutInit(&argc,argv);

    // We're going to animate it, so double buffer 
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );

    // Initial parameters for window position and size
    glutInitWindowPosition( 60, 60 );
    glutInitWindowSize( 600, 600 );
    
    camera.SetDimensions(600, 600);

    camera.SetDistance(20);
    camera.SetCenter(Vec3d(0,0,0));
    
    glutCreateWindow("Assignment 4");

    // Initialize OpenGL parameters.
    initRendering();

    // Set up callback functions for key presses
    glutKeyboardFunc(keyboardFunc); // Handles "normal" ascii symbols
    glutSpecialFunc(specialFunc);   // Handles "special" keyboard keys

    // Set up callback functions for mouse
    glutMouseFunc(mouseFunc);
    glutMotionFunc(motionFunc);

    // Set up the callback function for resizing windows
    glutReshapeFunc( reshapeFunc );

    // Call this whenever window needs redrawing
    glutDisplayFunc( drawScene );

    // Trigger timerFunc every 20 msec
    glutTimerFunc(20, timerFunc, 20);

        
    // Start the main loop.  glutMainLoop never returns.
    glutMainLoop( );

    return 0;	// This line is never reached.
}
    
